import java.util.*
import kotlin.collections.ArrayList
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField

/**
 * @Description: 用来复制元素的工具类
 * @Author: dk
 * @Date: 2021/6/28 12:05 PM
 * @Gmail: dk.guo@tron.network
 */
fun <T : Any> T.deepCopy(): T {
    val copiedObjects = mutableMapOf<Any, Any>()
    return deepCopy(this, copiedObjects)
}

private fun <T : Any> deepCopy(obj: T, copiedObjects: MutableMap<Any, Any>): T {

    val objClassJava = obj::class.java
    val objClass = obj::class

    // 基本数据类型直接返回
    if (objClassJava.isPrimitive) {
        return obj
    } else {
        if (objClass.javaPrimitiveType != null) {
            return obj
        }
    }

    when (obj) {
        is String -> {
            val newString = String(obj.toCharArray())
            copiedObjects[obj] = newString
            return newString as T
        }
        is Array<*> -> {
            val arrList = ArrayList<Any?>()
            val newArray: Any

            for (elem in obj) {
                if (elem == null)
                    arrList.add(elem)
                else
                    arrList.add(getValueFromCopiedCollection(elem, copiedObjects))
            }
            newArray = obj.clone()
            arrList.toArray(newArray)
            copiedObjects[obj] = newArray
            return newArray as T
        }
        is List<*> -> {
            val arrList = ArrayList<Any?>()
            for (elem in obj) {
                if (elem == null)
                    arrList.add(elem)
                else
                    arrList.add(getValueFromCopiedCollection(elem, copiedObjects))
                copiedObjects[obj] = arrList
            }
            return arrList as T
        }
        is Map<*, *> -> {
            val newMap = mutableMapOf<Any?, Any?>()
            for ((key, value) in obj) {
                if (key == null) {
                    if (value == null)
                        newMap[key] = value
                    else
                        newMap[key] = getValueFromCopiedCollection(value, copiedObjects)
                } else {
                    if (value === null)
                        newMap[getValueFromCopiedCollection(key, copiedObjects)] = value
                    else
                        newMap[getValueFromCopiedCollection(key, copiedObjects)] =
                            getValueFromCopiedCollection(value, copiedObjects)
                }
            }
            copiedObjects[obj] = newMap
            return newMap as T
        }
        is Set<*> -> {
            val newSet = mutableSetOf<Any?>()
            for (elem in obj) {
                if (elem == null)
                    newSet.add(elem)
                else
                    newSet.add(getValueFromCopiedCollection(elem, copiedObjects))
            }
            copiedObjects[obj] = newSet
            return newSet as T
        }
        is Date -> {
            return Date(obj.time) as T
        }
        else -> {
            val properties = objClass.memberProperties
            val newCopy = objClassJava.newInstance()

            properties.forEach { prop ->
                val field = prop.javaField
                if (field != null) {
                    field.isAccessible = true
                    val value = field.get(obj)
                    if (value == null)
                        field.set(newCopy, value)
                    else
                        field.set(newCopy, getValueFromCopiedCollection(value, copiedObjects))
                }
            }
            copiedObjects[obj] = newCopy
            return newCopy
        }
    }
}

private fun <T : Any> getValueFromCopiedCollection(
    value: T,
    copiedObjects: MutableMap<Any, Any>
): T {

    if (copiedObjects.containsKey(value)) {
        return copiedObjects[value] as T
    }

    var tempValue: Any? = null
    if (!value::class.java.isPrimitive
        && value::class.javaPrimitiveType == null
        && !value::class.java.isArray
        && (value !is Collection<*>)
    ) {

        tempValue = value::class.createInstance()
    }

    if (copiedObjects.isNotEmpty()) {
        tempValue = deepCopy(value, copiedObjects)
    }

    if (tempValue == null || tempValue != value)
        tempValue = value
    else
        copiedObjects[value] = tempValue

    return tempValue as T
}